/**
 * This panel represents the access to a chart's information and parameters.
 */
package org.hackystat.ui.telemetryviewer.client;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.ListIterator;

import org.hackystat.ui.telemetryviewer.client.common.BasicConveyor;
import org.hackystat.ui.telemetryviewer.client.common.DatasetParcel;
import org.hackystat.ui.telemetryviewer.client.common.DatasetConveyor;
import org.hackystat.ui.telemetryviewer.client.common.ProjectParcel;
import org.hackystat.ui.telemetryviewer.client.common.RelativeDates;
import org.hackystat.ui.telemetryviewer.client.common.Session;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FocusListener;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * This panel represents the access to a chart's information and parameters.
 * @author David J. Nickles
 *
 */
public class CompositeChartPanel extends HorizontalPanel 
  implements ClickListener, ChangeListener, FocusListener, WindowResizeListener {

  /** {@literal} POPUPTOPOFFSET the offset from the top of the date text box to position the 
   *    calendar popup. */
  private static final int POPUPTOPOFFSET =  20;

  private ArrayList parameters;
  private Button chartButton;
  private FlexTable table;
  private HorizontalPanel absoluteDateLine;
  private Label chartName;
  private Label description;
  private ListBox projectListBox;
  private ListBox dateModeListBox;
  private ListBox relativeDatesListBox;
  private PopupPanel startDatePopup;
  private PopupPanel endDatePopup;
  private Session session;
  private String chartParams;
  private TextBox startDate;
  private TextBox endDate;
  private VerticalPanel descriptionPanel;
  private VerticalPanel mainPanel;
  
  /**
   * Create a live chart from the Telemetry Analysis service.
   * 
   * @param session the current session attributes.
   * @param name the chart name.
   * @param description the chart description.
   */
  public CompositeChartPanel(Session session, String name, String description) {
    this.session = session;
    this.parameters = new ArrayList();

    setStyleName(Styles.BOTTOMPANEL);
    setWidth(Integer.toString(Window.getClientWidth() - Styles.RIGHTOFFSET) + Styles.PIXELSUFFIX);
    
    this.mainPanel = new VerticalPanel();
    this.mainPanel.add(this.chartName = new Label(name));
    this.mainPanel.add(this.descriptionPanel = new VerticalPanel());
    this.descriptionPanel.add(this.description = new Label(description));
    this.description.setStyleName(Styles.EXAMPLELABEL);
    VerticalPanel rule = new VerticalPanel();
    rule.setStyleName(Styles.RULE);
    this.mainPanel.add(rule);
    add(this.mainPanel);
    
    Window.addWindowResizeListener(this);
  }
  
  /**
   * Initialize this panel from RPC calls.
   */
  public void init() {
    RootPanel.get(CompositeWidget.CHART_HEADER_ELEMENT_ID).clear();
    RootPanel.get(CompositeWidget.CHART_ELEMENT_ID).clear();
    RootPanel.get("help").setVisible(false);
    RootPanel.get(CompositeWidget.CHART_SETTINGS_ELEMENT_ID).clear();
    RootPanel.get(CompositeWidget.CHART_SETTINGS_ELEMENT_ID).add(this);
    rpcGetChartDef();
  }
  
  /**
   * This method processes the get chart index result.
   * @param conveyor the data structure for the data streams.
   */
  private void processChartData(DatasetConveyor conveyor) {
    CompositeWidget.clearWaiting();
    if (Boolean.TRUE.toString().equals(conveyor.getStatus())) {
      if (conveyor.getStreams().size() == 0) {
        CompositeWidget.showAlert("No data streams received from " + this.chartName.getText() 
          + "  chart service.");
      }
      else {
        ChartFactory chart = new ChartFactory();
        if (conveyor.getStreams().size() == 1 && 
           ((DatasetParcel)conveyor.getStreams().get(0)).getDomain().length == 0) {
          CompositeWidget.showAlert("No data was reported for " + this.chartName.getText()
            + " in the specified date interval.");
        }
        XYDatasetFactory.getInstance().addDataset(this.chartName.getText(), conveyor);
        try {
          chart.chart();
          chart.report(conveyor.getMessage());
        }
        catch (Exception e) {
          CompositeWidget.showAlert("Chronoscope Reported An Error " + e.getCause() + " at " 
            + e.getStackTrace()[0].toString() + " adding telemetry stream " 
            + this.chartName.getText() + " to the chart.");
        }
      }
    }
    else {
      CompositeWidget.showAlert("Error at Servlet: " + conveyor.getStatus());
    }
  }
  
  /**
   * Check with the servlet to get the host name from the properties file.
   */
  private void rpcGetChartData() {
    String message = "Requesting telemetry stream(s) from " + this.chartName.getText() 
      + " chart service.";
    CompositeWidget.showWaiting(message);
    String start = null;
    String end = null;
    switch (this.dateModeListBox.getSelectedIndex()) {
      case 0:
        start = this.startDate.getText();
        end = this.endDate.getText();
        break;
      case 1:
      default:
        start = this.relativeDatesListBox.getItemText(this.relativeDatesListBox.getSelectedIndex());
        end = null;
    }
    RPCServiceFactory.getInstance().getService().getChartData(this.session, 
      this.chartName.getText(), this.chartParams, start, end,
      new AsyncCallback()
      {
        public void onSuccess(Object result) {
          processChartData((DatasetConveyor) result);
        }
    
        public void onFailure(Throwable caught) {
          if (caught instanceof com.google.gwt.user.client.rpc.InvocationException) {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, 
              "Cannot invoke getChartData on servlet.");
          } 
          else {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, caught.toString());
          }
        }
      });
  }  
  
  /**
   * Add a UI element for a particular chart attribute.
   * 
   * @param definition of a particular chart attribute.
   * @param row of the table to insert this attribute.
   * 
   * @gwt.typeArgs definition <java.lang.String>
   */
  private void addAttribute(ArrayList definition, int row) {
    String name = (String)definition.get(0);
    String description = (String)definition.get(1);
    String type = (String)definition.get(2);
    String value = (String)definition.get(3);
    String max = (String)definition.get(4);
    String min = (String)definition.get(5);
    
    this.table.setText(row, 0, name);
    if ("Text".equals(type)) {
      TextBox textbox = new TextBox();
      this.table.setWidget(row, 1, textbox);
      textbox.setText(value);
      this.parameters.add(textbox);
      textbox.addFocusListener(this);
    } 
    else if ("Boolean".equals(type)) {
      CheckBox checkbox = new CheckBox();
      this.table.setWidget(row, 1, checkbox);
      checkbox.setChecked("true".equals(value));
      this.parameters.add(checkbox);
      checkbox.addFocusListener(this);
    } 
    else if ("Integer".equals(type)) {
      TextBox textbox = new TextBox();
      this.table.setWidget(row, 1, textbox);
      textbox.setText(value);
      this.parameters.add(textbox);
      textbox.addFocusListener(this);
    } 
    else if ("Enumerated".equals(type)) {
      ListBox listbox = new ListBox();
      this.table.setWidget(row, 1, listbox);
      for (int i = 6; i < definition.size(); i++) {
        if (definition.get(i) != null) {
          listbox.addItem((String)definition.get(i));
        }
      }
      listbox.addItem(value);
      this.parameters.add(listbox);
      listbox.addFocusListener(this);
    } 
    else {
      TextBox textbox = new TextBox();
      this.table.setWidget(row, 1, textbox);
      textbox.setText(value);
      this.parameters.add(textbox);
      textbox.addFocusListener(this);
    } 
    this.table.setText(row, 2, description);
  }
  
  /**
   * Initialize the chart definition for this pane.
   * 
   * @param conveyor containing the chart attributes.
   */
  private void processChartDef(BasicConveyor conveyor) {
    CompositeWidget.clearWaiting();
    if (conveyor.getStatus().equals(Boolean.TRUE.toString())) {
      ArrayList definition = conveyor.getLists();
      this.table = new FlexTable();
      this.projectListBox = new ListBox(true);
      //this.projectListBox.setVisibleItemCount(this.session.getProjectList().size());
      this.projectListBox.setVisibleItemCount(5);
      this.projectListBox.setTitle("The project whose telemetry will be returned.");
      ListIterator projectsIterator = this.session.getProjectList().listIterator();
      while (projectsIterator.hasNext()) {
        ProjectParcel project = (ProjectParcel)projectsIterator.next();
        this.projectListBox.addItem(project.getName(), project.getDescription());
      }
      this.projectListBox.addChangeListener(this);
      this.projectListBox.addFocusListener(this);
      //this.session.markProjectSelected(this.projectListBox.getSelectedIndex());
      
      ProjectParcel project = (ProjectParcel)this.session.getProjectList().get(0);
      
      this.table.addStyleName(Styles.PARAMTABLE);
      this.table.setText(0, 0, "Project:");
      this.table.setWidget(0, 1, this.projectListBox);
      
      this.chartButton = new Button("Chart");
      this.chartButton.addClickListener(this);
      this.table.setWidget(0, 2, this.chartButton);
      
      this.dateModeListBox = new ListBox();
      this.dateModeListBox.setTitle("Switch between relative and absolute time settings.");
      this.dateModeListBox.addItem("Absolute");
      this.dateModeListBox.addItem("Relative");
      this.dateModeListBox.setVisibleItemCount(1);
      this.dateModeListBox.setSelectedIndex(1);
      this.dateModeListBox.addChangeListener(this);
      this.dateModeListBox.addFocusListener(this);
      this.table.setWidget(1, 0, this.dateModeListBox);
      
      this.relativeDatesListBox = new ListBox();
      this.relativeDatesListBox.setTitle("The relative time interval.");
      this.relativeDatesListBox.addItem(RelativeDates.LASTDAY);
      this.relativeDatesListBox.addItem(RelativeDates.LASTTWODAYS);
      this.relativeDatesListBox.addItem(RelativeDates.LASTWEEK);
      this.relativeDatesListBox.addItem(RelativeDates.LASTTWOWEEKS);
      this.relativeDatesListBox.addItem(RelativeDates.LASTMONTH);
      this.relativeDatesListBox.addItem(RelativeDates.LASTTWOMONTHS);
      this.relativeDatesListBox.setVisibleItemCount(1);
      this.relativeDatesListBox.setSelectedIndex(2);
      this.relativeDatesListBox.addFocusListener(this);
      this.table.setWidget(1, 1, this.relativeDatesListBox);

      this.startDate = new TextBox();
      this.table.setText(2, 0, "Start Date:");
      this.table.setWidget(2, 1, this.startDate);
      this.startDate.addClickListener(this);
      this.startDate.addChangeListener(this);
      this.startDate.addFocusListener(this);
      this.startDate.setText(project.getStartTime());
      (this.startDatePopup = new PopupPanel()).add(
        new CalendarWidget(this.session, this.startDate));

      this.endDate = new TextBox();
      this.table.setText(3, 0, "End Date:");
      this.table.setWidget(3, 1, this.endDate);
      this.endDate.addClickListener(this);
      this.endDate.addChangeListener(this);
      this.endDate.addFocusListener(this);
      //this.endDate.setText(project.getEndTime());
      this.endDate.setText(DateTimeFactories.getInstance().getDatefactory().format(new Date()));
      (this.endDatePopup = new PopupPanel()).add(
        new CalendarWidget(this.session, this.endDate));
      
      switch (this.dateModeListBox.getSelectedIndex()) {
        case 0: 
          this.relativeDatesListBox.setVisible(false);
          this.table.getRowFormatter().setVisible(2, true);
          this.table.getRowFormatter().setVisible(3, true);
          break;
        case 1:
        default:
          this.relativeDatesListBox.setVisible(true);
          this.table.getRowFormatter().setVisible(2, false);
          this.table.getRowFormatter().setVisible(3, false);
      }
      
      int i = 4;
      this.parameters = new ArrayList();
      if (definition == null) {
        this.chartParams = null;
      }
      else {
        StringBuffer paramsBuffer = null;
        if (definition.size() > 1) {
          Iterator it = definition.listIterator(1);
          while (it.hasNext()) {
            ArrayList attrSpecs = ((ArrayList)it.next());
            addAttribute(attrSpecs, i++); // add the attribute to the flextable
            String value = (String)attrSpecs.get(3);
            if (paramsBuffer == null) {
              paramsBuffer = new StringBuffer(value);
            }
            else {
              paramsBuffer.append(',');
              paramsBuffer.append(value);
            }
          }
          if (paramsBuffer == null) {
            this.chartParams = null;
          }
          else {
            this.chartParams = paramsBuffer.toString();
          }
        }
      }
      this.mainPanel.add(this.table);
    }
    else {
      CompositeWidget.showAlert(conveyor.getStatus());
    }
  }
  
  /**
   * Check with the servlet to get the host name from the properties file.
   */
  private void rpcGetChartDef() {
    CompositeWidget.showWaiting("Requesting telemetry parameters for chart " 
      + this.chartName.getText());
    RPCServiceFactory.getInstance().getService().getChartDefinition(this.session, 
      this.chartName.getText(), new AsyncCallback()
      {
        public void onSuccess(Object result) {
          processChartDef((BasicConveyor) result);
        }
    
        public void onFailure(Throwable caught) {
          if (caught instanceof com.google.gwt.user.client.rpc.InvocationException) {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, 
              "Cannot invoke getChartDef on servlet.");
          } 
          else {
            CompositeWidget.log(RPCServiceFactory.PRCERRORNAME, caught.toString());
          }
        }
      });
  }

  /**
   * Updates the selection-status of the projects.
   */
  private void refreshProjectSelections() {
    for (int i = 0; i < this.projectListBox.getItemCount(); i++) {
      Object obj = this.session.getProjectList().get(i);
      if (obj instanceof ProjectParcel) {
        ProjectParcel p = (ProjectParcel)obj;
        p.setSelected(Boolean.valueOf(this.projectListBox.isItemSelected(i)));
      }
    }
  }

  /**
   * Cycle through the UI widgets and setup the parameters.
   */
  private void generateParams() {
    this.chartParams = null;
    StringBuffer paramsBuffer = null;
    if (this.parameters.size() > 0) {
      Iterator it = this.parameters.listIterator();
      while (it.hasNext()) {
        Object widgetObj = it.next();
        String value = "";
        if (widgetObj instanceof TextBox) {
          value = ((TextBox)widgetObj).getText();
        }
        else if (widgetObj instanceof CheckBox) {
          if (((CheckBox)widgetObj).isChecked()) {
            value = "true";
          }
          else {
            value = "false";
          }
        }
        else if (widgetObj instanceof ListBox) {
          value = ((ListBox)widgetObj).getItemText(((ListBox)widgetObj).getSelectedIndex());
        }
        if (paramsBuffer == null) {
          paramsBuffer = new StringBuffer(value);
        }
        else {
          paramsBuffer.append(',');
          paramsBuffer.append(value);
        }
      }
      if (paramsBuffer == null) {
        this.chartParams = null;
      }
      else {
        this.chartParams = paramsBuffer.toString();
      }
    }
  }

  /**
   * This method responds to user clicks on listener-registered UI elements.
   * @param sender the widget clicked on.
   */
  public void onClick(Widget sender) {
    if (sender == this.chartButton) {
      this.startDatePopup.hide();
      this.endDatePopup.hide();
      generateParams();
      refreshProjectSelections();
      rpcGetChartData();
    }
  }

  /**
   * Callback method when a registered UI object's value is changed by the user manually.
   * @param sender the element changed.
   */
  public void onChange(Widget sender) {
    if (sender == this.dateModeListBox) {
      if (this.dateModeListBox.getSelectedIndex() == 0) {
        this.relativeDatesListBox.setVisible(false);
        this.table.getRowFormatter().setVisible(2, true);
        this.table.getRowFormatter().setVisible(3, true);
      } 
      else {
        this.relativeDatesListBox.setVisible(true);
        this.table.getRowFormatter().setVisible(2, false);
        this.table.getRowFormatter().setVisible(3, false);
        this.startDatePopup.hide();
        this.endDatePopup.hide();
      }
    }
    else if (sender == this.startDate) {
      this.startDatePopup.hide();
    }
    else if (sender == this.endDate) {
      this.endDatePopup.hide();
    }
    else if (sender == this.projectListBox) {
      this.session.markProjectSelected(this.projectListBox.getSelectedIndex());
      ProjectParcel project = (ProjectParcel)this.session.getProjectList().get(
        this.projectListBox.getSelectedIndex());
      this.startDate.setText(project.getStartTime());
      //this.endDate.setText(project.getEndTime());
      this.endDate.setText(DateTimeFactories.getInstance().getDatefactory().format(new Date()));
    }
  }

  /**
   * This method responds to gain of focus on listener-registered UI elements.
   * @param sender the widget clicked on.
   */
  public void onFocus(Widget sender) {
    if (sender == this.startDate) {
      this.startDatePopup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
        public void setPosition(int offsetWidth, int offsetHeight) {
          int left = startDate.getAbsoluteLeft();
          int top = startDate.getAbsoluteTop() + POPUPTOPOFFSET;
          startDatePopup.setPopupPosition(left, top);
        }
      });
      this.endDatePopup.hide();
    }
    else if (sender == this.endDate) {
      this.endDatePopup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
        public void setPosition(int offsetWidth, int offsetHeight) {
          int left = endDate.getAbsoluteLeft();
          int top = endDate.getAbsoluteTop() + POPUPTOPOFFSET;
          endDatePopup.setPopupPosition(left, top);
        }
      });
      this.startDatePopup.hide();
    }
    else {
      this.startDatePopup.hide();
      this.endDatePopup.hide();
    }
  }

  /**
   * This method responds to loss of focus on listener-registered UI elements.
   * This method is not used so it is intentionally left empty.
   * @param sender the widget clicked on.
   */
  public void onLostFocus(Widget sender) {
    // this method is intentionally left empty
  }

  /** 
   * Listens for user resizing of the window.
   * @param width of the adjusted window..
   * @param height of the adjusted window.
   */
  public void onWindowResized(int width, int height) {
    this.setWidth(Integer.toString(width - Styles.RIGHTOFFSET) + Styles.PIXELSUFFIX);
  }
}
